package com.wechat.pay.contrib.apache.httpclient;

import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.execchain.ClientExecChain;

import java.io.IOException;

/**
 * 请求执行器, 责任链模式, 请求和响应时会执行{@link #execute(HttpRoute, HttpRequestWrapper, HttpClientContext, HttpExecutionAware)}方法
 */
public class SignatureExec implements ClientExecChain {

    final ClientExecChain mainExec;
    final Credentials credentials;
    final Validator validator;

    SignatureExec(Credentials credentials, Validator validator, ClientExecChain mainExec) {
        this.credentials = credentials;
        this.validator = validator;
        this.mainExec = mainExec;
    }

    /**
     * 将应答的body内容转换为可重复的(可重复读取, 如果是流式则是不可重复, 所以将其转换成可重复);
     * 应答内容可能遇到是流式, 如下载图片, 这种内容只能读取一次, 为了可以多次读取, 则需要转换, 可以参考{@link BufferedHttpEntity}的doc;
     *
     * @param response 应答
     * @throws IOException 异常
     */
    protected void convertToRepeatableResponseEntity(CloseableHttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            response.setEntity(new BufferedHttpEntity(entity));
        }
    }

    /**
     * 将body转换成可重复的;
     *
     * @param request 请求
     * @throws IOException 异常
     */
    protected void convertToRepeatableRequestEntity(HttpRequestWrapper request) throws IOException {
        //此处if判断是为了判断请求对象是否带有entity
        if (request instanceof HttpEntityEnclosingRequest) {
            HttpEntityEnclosingRequest newRequest = (HttpEntityEnclosingRequest) request;
            HttpEntity entity = newRequest.getEntity();
            if (entity != null) {
                newRequest.setEntity(new BufferedHttpEntity(entity));
            }
        }
    }

    @Override
    public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware) throws IOException, HttpException {
        //判断是否是用于微信支付, 如果是才需要计算签名等操作, 不是就直接放行, 所以WechatPayHttpClientBuilder是可以用来处理其他请求的;
        if (request.getTarget().getHostName().endsWith(".mch.weixin.qq.com")) {
            return executeWithSignature(route, request, context, execAware);
        } else {
            return mainExec.execute(route, request, context, execAware);
        }
    }

    /**
     * 请求中添加签名操作, 应答中添加验签操作;
     *
     * @param route     上级链式执行器传入参数;
     * @param request   上级链式执行器传入参数;
     * @param context   上级链式执行器传入参数;
     * @param execAware 上级链式执行器传入参数;
     * @return {@link CloseableHttpResponse}
     */
    private CloseableHttpResponse executeWithSignature(HttpRoute route, HttpRequestWrapper request, HttpClientContext context, HttpExecutionAware execAware) throws IOException, HttpException {
        // 如果是上传图片等操作, entity虽是不可重复的, 但是不需要读取多次(不需要再次获取body内容用于签名, 因为在WechatPayUploadHttpPost对象中已经将需要签名的内容实现处理好了), 故不做转换
        if (!(request.getOriginal() instanceof WechatPayUploadHttpPost)) {
            convertToRepeatableRequestEntity(request);
        }
        // 添加认证信息
        request.addHeader(HeadersName.AUTHORIZATION, credentials.getSchema() + " " + credentials.getToken(request));

        // 执行
        CloseableHttpResponse response = mainExec.execute(route, request, context, execAware);

        // 对成功应答验签
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode >= 200 && statusCode < 300) {
            convertToRepeatableResponseEntity(response);
            if (!validator.validate(response)) {
                throw new HttpException("应答的微信支付签名验证失败");
            }
        }
        return response;
    }

}
